using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

using TInput = ContentExtentions.GravityModelHolder;
using TOutput = Microsoft.Xna.Framework.Content.Pipeline.Processors.ModelContent;

namespace ContentExtentions
{
    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to apply custom processing to content data, converting an object of
    /// type TInput to TOutput. The input and output types may be the same if
    /// the processor wishes to alter data without changing its type.
    ///
    /// This should be part of a Content Pipeline Extension Library project.
    /// </summary>
    [ContentProcessor(DisplayName = "Gravity Normal Rotate Model Processor")]
    public class GravityNormalRotateProcessor : ContentProcessor<TInput, TOutput>
    {
        private float minX = float.MaxValue;
        private float minY = float.MaxValue;
        private float minZ = float.MaxValue;
        private float maxX = float.MinValue;
        private float maxY = float.MinValue;
        private float maxZ = float.MinValue;

        private Matrix totalNormalRotation;

        public float RotateNormalX
        {
            get;
            set;
        }

        public float RotateNormalY
        {
            get;
            set;
        }

        public float RotateNormalZ
        {
            get;
            set;
        }

        public override TOutput Process(TInput input, ContentProcessorContext context)
        {
            // Make the matrix we will use to rotate the normals
            totalNormalRotation = Matrix.CreateRotationX( MathHelper.ToRadians( RotateNormalX ) ) *
                                  Matrix.CreateRotationY( MathHelper.ToRadians( RotateNormalY ) ) *
                                  Matrix.CreateRotationZ( MathHelper.ToRadians( RotateNormalZ ) );

            // Do whatever we need to do to the content
            this.GetBoundingBoxAndRotateNormals( input.ModelRootNode );

            // Make the boundingbox we just calculated
            BoundingBox boundingBox = new BoundingBox( new Vector3( minX, minY, minZ ), new Vector3( maxX, maxY, maxZ ) );

            // Let the base class process this content as a model
            ModelProcessor modelProcessor = new ModelProcessor();
            ModelContent modelContent = modelProcessor.Process(input.ModelRootNode, context);

            // Add the gravity to each mesh as a tag
            foreach (ModelMeshContent modelMeshContent in modelContent.Meshes)
            {
                modelMeshContent.Tag = input.Gravity;
            }

            // Add the boundingbox to the model content as a tag
            modelContent.Tag = boundingBox;

            // return the model, with boundingbox and rotated normals
            return modelContent;
        }

        private void GetBoundingBoxAndRotateNormals(NodeContent node)
        {
            // Try to cast the content to mesh content. Later we can see if it worked (thus, it must be mesh content)
            MeshContent meshContent = node as MeshContent;

            // If this content is mesh content, we can use it to do something with it
            if (meshContent != null)
            {
                // For every position (vertex) of the mesh content, we will calculate the minimum and maximum
                // x, y and z coordinates. This will form the boundingbox later on
                foreach (Vector3 position in meshContent.Positions)
                {
                    if (position.X < minX)
                    {
                        minX = position.X;
                    }
                    if (position.Y < minY)
                    {
                        minY = position.Y;
                    }
                    if (position.Z < minZ)
                    {
                        minZ = position.Z;
                    }
                    if (position.X > maxX)
                    {
                        maxX = position.X;
                    }
                    if (position.Y > maxY)
                    {
                        maxY = position.Y;
                    }
                    if (position.Z > maxZ)
                    {
                        maxZ = position.Z;
                    }
                }

                // Rotate the normals by looking in the geometry contents of this mesh content
                foreach (GeometryContent geometryContent in meshContent.Geometry)
                {
                    // For all the normals in this geometry content we must rotate them
                    for (int i = 0; i < geometryContent.Vertices.Channels[VertexChannelNames.Normal()].Count; i++)
                    {
                        // Here we actually rotate the normals
                        geometryContent.Vertices.Channels[VertexChannelNames.Normal()][i] = Vector3.Transform(
                                (Vector3)geometryContent.Vertices.Channels[VertexChannelNames.Normal()][i],
                                totalNormalRotation);
                    }
                }
            }
            else
            {
                // If this is not a mesh content, we don't want to use it, so look at the children
                foreach (NodeContent childNode in node.Children)
                {
                    this.GetBoundingBoxAndRotateNormals( childNode );
                }
            }
        }
    }
}